package com.alibaba.citrus.ots.platform.enhance.facade.service.diff.inventory;

import com.alibaba.citrus.ots.b2b.trade.order.repository.OrderLineQueryServiceRepository;
import com.alibaba.citrus.ots.b2b.trade.order.repository.OrderQueryRepository;
import com.alibaba.citrus.ots.common.annotation.ProcessInvoker;
import com.alibaba.citrus.ots.platform.enhance.facade.service.diff.common.constant.DiffResultStatus;
import com.alibaba.citrus.ots.platform.enhance.facade.service.diff.common.constant.DiffTypeEnum;
import com.alibaba.citrus.ots.platform.enhance.facade.service.diff.inventory.constant.BusiInvActionType;
import com.alibaba.citrus.ots.platform.enhance.facade.service.diff.inventory.constant.InventoryProcessResult;
import com.alibaba.citrus.ots.platform.enhance.functions.diff.common.constant.DiffStrategyRouteDict;
import com.alibaba.citrus.ots.platform.enhance.repository.DiffErrorRepository;
import com.alibaba.citrus.ots.platform.enhance.repository.model.diff.common.DiffErrorDTO;
import com.alibaba.cz.base.tool.Nullable;
import com.alibaba.cz.base.tool.log.Log;
import com.alibaba.fastjson.JSON;
import com.alibaba.lattice2.epoch.sdk.model.Result;
import com.epoch.app.otsb2btradecenter.dto.response.OrderLinePageQueryResponse;
import com.epoch.app.otsb2btradecenter.model.dto.PageQueryOrderLineESListRequest;
import com.epoch.app.otsplatformenhance.inventorydiffcheck.service.InventoryDiffCheckService;
import com.epoch.app.otsplatformenhance.model.dto.InventoryDiffCheckRequest;
import com.epoch.app.otsplatformenhance.model.dto.InventoryDiffSourceData;
import com.epoch.app.otsunifiedinventory.model.dto.*;
import com.epoch.app.otsunifiedinventory.service.OtsUnifiedInventoryService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Primary
public class InventoryDiffCheckServiceImpl implements InventoryDiffCheckService {
    public static final String DATE_FORMAT = "yyyy-MM-dd";
    public static final String DATE_FORMAT_HMS = "yyyy-MM-dd HH:mm:ss";
    private Log log = Log.getLogger(InventoryDiffCheckServiceImpl.class);
    @Resource
    private OrderQueryRepository orderQueryRepository;
    @Resource
    private OrderLineQueryServiceRepository lineQueryServiceRepository;
    @Resource
    private OtsUnifiedInventoryService otsUnifiedInventoryService;
    @Resource
    private DiffErrorRepository diffErrorRepository;


    @Override
    public Result<String> inventoryOccupyDiffCheck(InventoryDiffCheckRequest inventoryDiffCheckRequest) {
        int limit=1000;


        if (!validRequestParams(inventoryDiffCheckRequest).isSuccess()){
            return validRequestParams(inventoryDiffCheckRequest);
        }
        //有占用记录集合
        List<BusinessStageInventoryDO> occupiedList = new ArrayList<>();
        //无占用记录集合
        List<OrderLinePageQueryResponse> unOccupiedList = new ArrayList<>();
        //对账结果
        List<DiffErrorDTO> diffErrorDTOList = new ArrayList<>();
        //销售子单ID集合
        //List<String> subOrderList = Lists.newArrayList();
        //销售主单ID集合
        List<String> orderList = Lists.newArrayList();
        //销售单映射
        //Map<String, OrderLinePageQueryResponse> orderLineMap = new HashMap<>();
        //销售单 主单-货品 维度映射
        Map<String, List<OrderLinePageQueryResponse>> orderLineMSMap = new HashMap<>();
        //子单集合
        List<OrderLinePageQueryResponse> orderLineList=new ArrayList<>();


        PageQueryOrderLineESListRequest request = new PageQueryOrderLineESListRequest();
        request.setStart(0);
        request.setLimit(limit);
        List<String> dates = new ArrayList<>();
//        dates.add(getThreeDaysAgo());
//        dates.add(getNowDay());
        dates.add(getStringDay(inventoryDiffCheckRequest.getStartDiffDate()));
        dates.add(getStringDay(inventoryDiffCheckRequest.getEndDiffDate()));
        request.setOrderTime(dates);
        request.setOrderLineTags(Arrays.asList("normal","exchange"));
        //应该排除退货
        request.setSubOrderStatusList(Arrays.asList("2", "3", "4", "5", "6", "7", "11", "12", "13", "14"));
        Result<List<OrderLinePageQueryResponse>> listResult = lineQueryServiceRepository.pageQueryES(request);
        if (Objects.isNull(listResult)||!listResult.isSuccess()){
            log.error("Inventory Diff Check Error!,Can not get order success response,{}",listResult);
            return Result.success(String.valueOf(Boolean.FALSE));
        }
        int total = listResult.getTotal();
        if (total>limit){
            int pageSize = total / limit;
            for (int i = 0; i <= pageSize; i++) {
                request.setStart(i*limit);
                request.setLimit(limit);
                Result<List<OrderLinePageQueryResponse>> orderLineResult = lineQueryServiceRepository.pageQueryES(request);
                if (Objects.nonNull(orderLineResult)&&CollectionUtils.isNotEmpty(orderLineResult.getResult())){
                    orderLineList.addAll(orderLineResult.getResult());
                }
            }
        }else {
            orderLineList.addAll(listResult.getResult());
        }

        if (CollectionUtils.isNotEmpty(orderLineList)) {
           // subOrderList = orderLineList.stream().map(OrderLinePageQueryResponse::getId).collect(Collectors.toList());
            orderList = orderLineList.stream().map(OrderLinePageQueryResponse::getOrderId).collect(Collectors.toList());
           // orderLineMap = orderLineList.stream().collect(Collectors.toMap(OrderLinePageQueryResponse::getId, Function.identity()));
            orderLineMSMap = orderLineList.stream().collect(Collectors.groupingBy(o -> o.getOrderId()+":"+o.getScItemId()));
            for (OrderLinePageQueryResponse orderPageQueryResponse : orderLineList) {
                GetInventoryStageRequest getInventoryStageRequest = new GetInventoryStageRequest();
                getInventoryStageRequest.setReferenceOrderId(orderPageQueryResponse.getOrderId());
                //getInventoryStageRequest.setReferenceSubOrderId(orderPageQueryResponse.getId());
                getInventoryStageRequest.setGoodsId(orderPageQueryResponse.getScItemId());
                getInventoryStageRequest.setActionType("OCCUPY");
                GetInventoryStageResponse inventoryStageOccupy = otsUnifiedInventoryService.getInventoryStage(getInventoryStageRequest);
                getInventoryStageRequest.setActionType("OCCUPY_RELEASE");
                GetInventoryStageResponse inventoryStageRelease = otsUnifiedInventoryService.getInventoryStage(getInventoryStageRequest);
                if (Objects.nonNull(inventoryStageOccupy) && CollectionUtils.isNotEmpty(inventoryStageOccupy.getResult())) {
                    long sum = inventoryStageOccupy.getResult().stream().mapToLong(BusinessStageInventoryDO::getWithholdingQuantity).sum();
                    inventoryStageOccupy.getResult().get(0).setWithholdingQuantity(sum);
                    //add to next step list ,calculation quantity
                    occupiedList.add(inventoryStageOccupy.getResult().get(0));
                }

                if (Objects.nonNull(inventoryStageRelease) && CollectionUtils.isNotEmpty(inventoryStageRelease.getResult())) {
                    long sum = inventoryStageRelease.getResult().stream().mapToLong(BusinessStageInventoryDO::getWithholdingReleaseQuantity).sum();
                    inventoryStageRelease.getResult().get(0).setWithholdingQuantity(sum);
                    //add to next step list ,calculation quantity
                    occupiedList.add(inventoryStageRelease.getResult().get(0));
                }

               if ((Objects.isNull(inventoryStageOccupy)&&Objects.isNull(inventoryStageRelease))||(CollectionUtils.isEmpty(inventoryStageOccupy.getResult())&&CollectionUtils.isEmpty(inventoryStageRelease.getResult()))){
                    //add to exception list,to notice warning
                    unOccupiedList.add(orderPageQueryResponse);
                }
            }
        }
        Map<String, List<BusinessInventoryFlowOrderDO>> flowOccupyOrderList = getFlowOrderList(orderList,BusiInvActionType.OCCUPY);
        Map<String, List<BusinessInventoryFlowOrderDO>> flowReleaseOrderList = getFlowOrderList(orderList,BusiInvActionType.OCCUPY_RELEASE);

        if (CollectionUtils.isNotEmpty(occupiedList)) {
            for (BusinessStageInventoryDO occupy : occupiedList) {
                //String outSubOrderId = occupy.getReferenceSubOrderId();
                String flowKey = occupy.getReferenceOrderId()+":"+occupy.getGoodsId();

                DiffErrorDTO diffErrorDTO = doDiff(occupy, flowOccupyOrderList.get(flowKey),flowReleaseOrderList.get(flowKey),orderLineMSMap.get(flowKey));
                if (Objects.nonNull(diffErrorDTO)){
                    diffErrorDTOList.add(diffErrorDTO);
                }
            }
        }
        if (CollectionUtils.isNotEmpty(unOccupiedList)) {
            for (OrderLinePageQueryResponse unOccupy : unOccupiedList) {
                DiffErrorDTO diffErrorDTO = buildDiffError(unOccupy.getOrderId(), unOccupy.getId(), null, null, InventoryProcessResult.NO_OCCUPY_RECORD.getResult(), null, DiffStrategyRouteDict.INVENTORY_DIFF_SALES_ORDER_LINE.getStrategyCode(),DiffResultStatus.FAIL,null,null);
                diffErrorDTOList.add(diffErrorDTO);
            }

        }
        //保存错误对账记录
        if (CollectionUtils.isNotEmpty(diffErrorDTOList)) {
            //批量保存差错记录
            diffErrorRepository.batchSaveDiffErrorList(diffErrorDTOList);
        }
        return Result.success(String.valueOf(Boolean.TRUE));
    }

    @Override
    public Result<String> retryDiff(InventoryDiffSourceData sourceData){
        DiffErrorDTO errorDTO;
        if (Objects.isNull(sourceData)){
            return null;
        }
        Map<String ,List<BusinessInventoryFlowOrderDO>> occupyFlows = new HashMap<>();
        Map<String ,List<BusinessInventoryFlowOrderDO>> releaseFlows = new HashMap<>();
        GetBizinventoryFlowForCheck2Request flowForCheck2Request = new GetBizinventoryFlowForCheck2Request();
        flowForCheck2Request.setStart(0);
        flowForCheck2Request.setLimit(1000);
        flowForCheck2Request.setOutReferenceOrderIds(Collections.singletonList(sourceData.getMainOrderId()));
        flowForCheck2Request.setOperationType(sourceData.getDiffType());
        GetBizinventoryFlowForCheck2Response response = otsUnifiedInventoryService.getBizinventoryFlowForCheck2(flowForCheck2Request);

        PageQueryOrderLineESListRequest request = new PageQueryOrderLineESListRequest();
        request.setStart(0);
        request.setLimit(1000);
        request.setOrderLineTags(Arrays.asList("normal","exchange"));
        request.setId(sourceData.getMainOrderId());
        request.setGoodsId(sourceData.getGoodsId());
        //应该排除退货
        request.setSubOrderStatusList(Arrays.asList("2", "3", "4", "5", "6", "7", "11", "12", "13", "14"));
        Result<List<OrderLinePageQueryResponse>> listResult = lineQueryServiceRepository.pageQueryES(request);

        GetInventoryStageRequest getInventoryStageRequest = new GetInventoryStageRequest();
        getInventoryStageRequest.setId(sourceData.getStageInventoryId());
        GetInventoryStageResponse inventoryStageRelease = otsUnifiedInventoryService.getInventoryStage(getInventoryStageRequest);

        if ((Objects.isNull(response)||CollectionUtils.isEmpty(response.getResult()))
                ||(Objects.isNull(listResult)||CollectionUtils.isEmpty(listResult.getResult()))
                ||(Objects.isNull(inventoryStageRelease)||CollectionUtils.isEmpty(inventoryStageRelease.getResult()))){
            errorDTO= buildDiffError(sourceData.getMainOrderId(), sourceData.getOrderLineIds().get(0), null, null, InventoryProcessResult.RETRY_DATA_MISSING.getResult(), null, DiffStrategyRouteDict.INVENTORY_DIFF_SALES_ORDER_LINE.getStrategyCode(),DiffResultStatus.FAIL,sourceData.getStageInventoryBatchNo(),JSON.toJSONString(sourceData));
        }else {
            if (sourceData.getDiffType().equals(BusiInvActionType.OCCUPY.getDesc())){
                occupyFlows=response.getResult().stream().collect(Collectors.groupingBy(BusinessInventoryFlowOrderDO::getGoodsId));
            }else {
                releaseFlows=response.getResult().stream().collect(Collectors.groupingBy(BusinessInventoryFlowOrderDO::getGoodsId));
            }

            errorDTO= doDiff(inventoryStageRelease.getResult().get(0),occupyFlows.get(sourceData.getGoodsId()),releaseFlows.get(sourceData.getGoodsId()),listResult.getResult());
        }


        //保存错误对账记录
        if (Objects.nonNull(errorDTO)) {
            //批量保存差错记录
            diffErrorRepository.batchSaveDiffErrorList(Collections.singletonList(errorDTO));
        }
        return Result.success(String.valueOf(Boolean.TRUE));

    }
    @ProcessInvoker
    public DiffErrorDTO doDiff(BusinessStageInventoryDO stageInventoryDO, List<BusinessInventoryFlowOrderDO> occupyFlows, List<BusinessInventoryFlowOrderDO> releaseFlows,  List<OrderLinePageQueryResponse> orderLines) {
        //Diff type,occupy or release ,different
        String actionType = stageInventoryDO.getActionType();

        InventoryDiffSourceData sourceData = builderSourceData(stageInventoryDO.getId(),occupyFlows,releaseFlows,orderLines,actionType);
        String jsonSourceData = JSON.toJSONString(sourceData);
        if (actionType.equals(BusiInvActionType.OCCUPY.getDesc())&&CollectionUtils.isEmpty(occupyFlows)) {
            return buildDiffError(orderLines.get(0).getOrderId(), orderLines.get(0).getId(), null, null, InventoryProcessResult.NO_OCCUPY_FLOW.getResult(), null, DiffStrategyRouteDict.INVENTORY_DIFF_SALES_ORDER_LINE.getStrategyCode(),DiffResultStatus.FAIL,stageInventoryDO.getBatchNo(),jsonSourceData);
        }
        if (actionType.equals(BusiInvActionType.OCCUPY_RELEASE.getDesc())&&CollectionUtils.isEmpty(releaseFlows)) {
            return buildDiffError(orderLines.get(0).getOrderId(), orderLines.get(0).getId(), null, null, InventoryProcessResult.NO_OCCUPY_RELEASE_FLOW.getResult(), null, DiffStrategyRouteDict.INVENTORY_DIFF_SALES_ORDER_LINE.getStrategyCode(),DiffResultStatus.FAIL,stageInventoryDO.getBatchNo(),jsonSourceData);
        }
        //sale order quantity
       // Long orderQuantity = Long.parseLong(orderLine.getOrderQuantity())*1000;
        Long orderQuantity = (orderLines.stream().mapToLong(o-> Long.parseLong(o.getOrderQuantity())).sum())*1000;

        //inventory stage quantity
        Long withholdingQuantity = stageInventoryDO.getWithholdingQuantity();

        if (!orderQuantity.equals(withholdingQuantity)){
            return  buildDiffError(orderLines.get(0).getOrderId(), orderLines.get(0).getId(), null, null, InventoryProcessResult.OCCUPY_QUANTITY_NOT_EQUAL.getResult(), String.valueOf(stageInventoryDO.getInventoryId()), DiffStrategyRouteDict.INVENTORY_DIFF_SALES_ORDER_LINE.getStrategyCode(),DiffResultStatus.FAIL,stageInventoryDO.getBatchNo(),jsonSourceData);
        }
        //compare

        // getFlowOrderList(); beforeWithholdingQuantity & afterWithholdingQuantity


        if (actionType.equals(BusiInvActionType.OCCUPY.getDesc())){
            List<BusinessInventoryFlowOrderDO> sortedFlow = occupyFlows.stream().sorted(Comparator.comparing(BusinessInventoryFlowOrderDO::getGmtCreate).reversed()).collect(Collectors.toList());
            Long flowWithholdingQuantity = Nullable.stream(sortedFlow).mapToLong(BusinessInventoryFlowOrderDO::getQuantity).sum();
            Long beforeWithholdingQuantity = Nullable.stream(sortedFlow).mapToLong(BusinessInventoryFlowOrderDO::getBeforeWithholdingQuantity).sum();
            Long afterWithholdingQuantity = Nullable.stream(sortedFlow).mapToLong(BusinessInventoryFlowOrderDO::getAfterWithholdingQuantity).sum();

            Long diffWithHoldingQuantity=afterWithholdingQuantity-beforeWithholdingQuantity;
            if (!(withholdingQuantity.equals(flowWithholdingQuantity)&&flowWithholdingQuantity.equals(diffWithHoldingQuantity))){
              //  return  buildDiffError(orderLine.getOrderId(), orderLine.getId(), null, null, InventoryProcessResult.OCCUPY_SUCCESS.getResult(), String.valueOf(stageInventoryDO.getInventoryId()), DiffStrategyRouteDict.INVENTORY_DIFF_SALES_ORDER_LINE.getStrategyName(),DiffResultStatus.SUCCESS,stageInventoryDO.getBatchNo(),jsonSourceData);
                return  buildDiffError(orderLines.get(0).getOrderId(), orderLines.get(0).getId(), null, null, InventoryProcessResult.OCCUPY_CALCULATE_WRONG.getResult(), String.valueOf(stageInventoryDO.getInventoryId()), DiffStrategyRouteDict.INVENTORY_DIFF_SALES_ORDER_LINE.getStrategyCode(),DiffResultStatus.FAIL,stageInventoryDO.getBatchNo(),jsonSourceData);

            }
        }else if(actionType.equals(BusiInvActionType.OCCUPY_RELEASE.getDesc())){
            List<BusinessInventoryFlowOrderDO> sortedFlow = releaseFlows.stream().sorted(Comparator.comparing(BusinessInventoryFlowOrderDO::getGmtCreate).reversed()).collect(Collectors.toList());
            Long flowWithholdingQuantity = Nullable.stream(sortedFlow).mapToLong(BusinessInventoryFlowOrderDO::getQuantity).sum();
            Long beforeWithholdingQuantity = Nullable.stream(sortedFlow).mapToLong(BusinessInventoryFlowOrderDO::getBeforeWithholdingQuantity).sum();
            Long afterWithholdingQuantity = Nullable.stream(sortedFlow).mapToLong(BusinessInventoryFlowOrderDO::getAfterWithholdingQuantity).sum();

            Long diffWithHoldingQuantity=beforeWithholdingQuantity-afterWithholdingQuantity;
            if (!(withholdingQuantity.equals(flowWithholdingQuantity)&&flowWithholdingQuantity.equals(diffWithHoldingQuantity))){
             //   return  buildDiffError(orderLine.getOrderId(), orderLine.getId(), null, null, InventoryProcessResult.OCCUPY_SUCCESS.getResult(), String.valueOf(stageInventoryDO.getInventoryId()), DiffStrategyRouteDict.INVENTORY_DIFF_SALES_ORDER_LINE.getStrategyName(),DiffResultStatus.SUCCESS,stageInventoryDO.getBatchNo(),jsonSourceData);
                return  buildDiffError(orderLines.get(0).getOrderId(), orderLines.get(0).getId(), null, null, InventoryProcessResult.OCCUPY_CALCULATE_WRONG.getResult(), String.valueOf(stageInventoryDO.getInventoryId()), DiffStrategyRouteDict.INVENTORY_DIFF_SALES_ORDER_LINE.getStrategyCode(),DiffResultStatus.FAIL,stageInventoryDO.getBatchNo(),jsonSourceData);

            }
        }
        return null;
    }

    private InventoryDiffSourceData builderSourceData(Long stageInventoryDO, List<BusinessInventoryFlowOrderDO> occupyFlows, List<BusinessInventoryFlowOrderDO> releaseFlows, List<OrderLinePageQueryResponse> orderLines, String actionType) {
        return InventoryDiffSourceData.builder()
                .mainOrderId(orderLines.get(0).getOrderId())
                //.quantity(orderLine.getOrderQuantity())
                .goodsId(orderLines.get(0).getScItemId())
                .releaseFlowIds(Nullable.stream(releaseFlows).map(BusinessInventoryFlowOrderDO::getId).collect(Collectors.toList()))
                .occupyFlowIds(Nullable.stream(occupyFlows).map(BusinessInventoryFlowOrderDO::getId).collect(Collectors.toList()))
                .orderLineIds(Nullable.stream(orderLines).map(OrderLinePageQueryResponse::getId).collect(Collectors.toList()))
                .diffType(actionType)
                .stageInventoryId(stageInventoryDO)
                .build();
    }



    private Map<String, List<BusinessInventoryFlowOrderDO>> getFlowOrderList(List<String> orderList, BusiInvActionType busiInvActionType) {
        GetBizinventoryFlowForCheck2Request flowForCheck2Request = new GetBizinventoryFlowForCheck2Request();
        flowForCheck2Request.setStart(0);
        flowForCheck2Request.setLimit(1000);
        flowForCheck2Request.setOutReferenceOrderIds(orderList);
        flowForCheck2Request.setOperationType(busiInvActionType.getDesc());
        GetBizinventoryFlowForCheck2Response response = otsUnifiedInventoryService.getBizinventoryFlowForCheck2(flowForCheck2Request);
        if (Objects.nonNull(response) && CollectionUtils.isNotEmpty(response.getResult())) {
            Map<String, List<BusinessInventoryFlowOrderDO>> collect = response.getResult().stream().collect(Collectors.groupingBy(o-> o.getReferenceOrderId() + ":" + o.getGoodsId()));
            return collect;
        }
        return new HashMap<>();
    }

    public DiffErrorDTO buildDiffError(String mainOrderId, String subOrderId, String data1, String data2, String result, String bInvId, String route,DiffResultStatus diffResultStatus,String sequence,String features) {
        DiffErrorDTO build = DiffErrorDTO.builder()
                .diffType(DiffTypeEnum.INVENTORY_DIFF_SALES_ORDER_LINE.getDiffType())
                .mainOrderId(mainOrderId)
                .subOrderId(subOrderId)
                .diffData1(data1)
                .diffData2(data2)
                .status(diffResultStatus.getStatus())
                .result(result)
                .businessInventoryId(bInvId)
                .sequence(sequence)
                .strategyRoute(route)
                .features(features)
                .build();

        return build;
    }

    private String getThreeDaysAgo() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT);
        Calendar instance = Calendar.getInstance();
        instance.set(Calendar.DAY_OF_MONTH, instance.get(Calendar.DAY_OF_MONTH) - 3);
        return simpleDateFormat.format(instance.getTime());
    }

    private String getNowDay() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT);

        return simpleDateFormat.format(new Date());
    }

    private String getStringDay(Date date){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT_HMS);

        return simpleDateFormat.format(date);
    }

    private Result<String> validRequestParams(InventoryDiffCheckRequest diffCheckRequest){
        if (Objects.isNull(diffCheckRequest)){
            return Result.fail("","request params can not be null");
        }

        if(Objects.isNull(diffCheckRequest.getLimit())){
            return Result.fail("","request params 'limit' can not be null");
        }

        if(Objects.isNull(diffCheckRequest.getStartDiffDate())){
            return Result.fail("","request params 'startDiffDate' can not be null");
        }

        if(Objects.isNull(diffCheckRequest.getEndDiffDate())){
            return Result.fail("","request params 'endDiffDate' can not be null");
        }

        if (diffCheckRequest.getStartDiffDate().after(diffCheckRequest.getEndDiffDate())){
            return Result.fail("","request params 'startDiffDate' can not bigger than 'endDiffDate' ");
        }

        return Result.success(String.valueOf(Boolean.TRUE));
    }
}
